#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.Threading.Tasks;
using BitwiseOre.SharedMemory;
using BitwiseOre.SharedMemory.Core;
using BitwiseOre.SharedMemory.Utils;
using Moq;
using NUnit.Framework;

namespace BitwiseOre.Tests.SharedMemory
{
    [TestFixture]
    public sealed class SharedMemoryStreamFixture
    {
        [Test(Description = "Checks that disposing a shared memory stream delegates to the dispoer")]
        public void Dispose_DelegatesToDispoer()
        {
            var disposer = new Mock<IDisposer<SharedMemoryStream>>();
            new SharedMemoryStream(disposer.Object, null, null, null, null, null).Dispose();

            disposer.Verify(d => d.Dispose(), "Disposing did not delegate to the disposer.");
        }

        [Test(Description = "Writing a partial array delegates to the writer.")]
        public void Write_DelegatesToWriter()
        {
            var buffer = new byte[] {1, 2, 3, 4, 5};
            const int offset = 2;
            const int count = 3;
            var writer = new Mock<IWriter>();
            writer.Setup(w => w.Write(It.IsAny<ArraySegment<byte>>(), It.IsAny<TimeSpan>()))
                .Returns(new Task(() => { }));

            var stream = new SharedMemoryStream(new Mock<IDisposer<SharedMemoryStream>>().Object, writer.Object,
                                                null, null, null, null);
            stream.Write(buffer, offset, count);

            writer.Verify(
                w =>
                w.Write(It.Is<ArraySegment<byte>>(a => a.Array == buffer && a.Count == count && a.Offset == offset),
                        It.IsAny<TimeSpan>()),
                "Write not called on the writer.");
        }

        [Test(Description = "Writing a partial array propegates a timeout exception thrown by the writer.")]
        public void Write_PropegatesTimeoutException()
        {
            var writer = new Mock<IWriter>();
            writer.Setup(d => d.Write(It.IsAny<ArraySegment<byte>>(), It.IsAny<TimeSpan>()))
                .Returns(new Task(() => { throw new TimeoutException(); }));

            var stream = new SharedMemoryStream(new Mock<IDisposer<SharedMemoryStream>>().Object, writer.Object,
                                                null, null, null, null);

            Assert.That(() =>
                            {
                                stream.Write(new byte[5], 0, 5);
                                return null;
                            },
                        Throws.InstanceOf<TimeoutException>(),
                        "Writing did not throw the expected exception.");
        }

        [Test(Description = "Writing a partial array propegates exceptions other than timeout exceptions " +
                            "thrown by the writer.")]
        public void Write_PropegatesGeneralException()
        {
            var writer = new Mock<IWriter>();
            writer.Setup(d => d.Write(It.IsAny<ArraySegment<byte>>(), It.IsAny<TimeSpan>()))
                .Returns(new Task(() => { throw new InvalidOperationException(); }));

            var stream = new SharedMemoryStream(new Mock<IDisposer<SharedMemoryStream>>().Object, writer.Object,
                                                null, null, null, null);

            Assert.That(() =>
                            {
                                stream.Write(new byte[5], 0, 5);
                                return null;
                            },
                        Throws.InstanceOf<SharedMemoryException>(),
                        "Writing did not throw the expected exception.");
            
        }

        [Test(Description = "Begining an async write operation delgates to the writer.")]
        public void BeginWrite_DelgatesToWriter()
        {
            var buffer = new byte[] {1, 2, 3, 4, 5};
            const int offset = 2;
            const int count = 3;
            var writer = new Mock<IWriter>();
            writer.Setup(w => w.Write(It.IsAny<ArraySegment<byte>>(), It.IsAny<TimeSpan>()))
                .Returns(new Task(() => { }));

            var stream = new SharedMemoryStream(new Mock<IDisposer<SharedMemoryStream>>().Object, writer.Object, null, null, null, null);
            stream.BeginWrite(buffer, offset, count, null, null);

            writer.Verify(
                w =>
                w.Write(It.Is<ArraySegment<byte>>(a => a.Array == buffer && a.Count == count && a.Offset == offset),
                        It.IsAny<TimeSpan>()),
                "Write not called on the writer.");
            
        }

        [Test(Description = "Writing a byte delegates to the writer.")]
        public void WriteByte_DelegatesToWriter()
        {
            var writer = new Mock<IWriter>();
            const byte value = 5;

            var stream = new SharedMemoryStream(new Mock<IDisposer<SharedMemoryStream>>().Object, writer.Object, null, null, null, null);
            stream.WriteByte(value);

            writer.Verify(w => w.WriteByte(It.Is<byte>(v => v == value), It.IsAny<TimeSpan>()),
                          "WriteByte not called on the writer.");
        }

        [Test(Description = "Flushing the stream delegates to the writer.")]
        public void Flush_DelegatesToWriter()
        {
            var writer = new Mock<IWriter>();

            var stream = new SharedMemoryStream(new Mock<IDisposer<SharedMemoryStream>>().Object, writer.Object,
                                                new Mock<IReader>().Object, null, null, null);
            stream.Flush();

            writer.Verify(w => w.Flush(),"Flush not called on the writer.");
        }

        [Test(Description = "Flushing the stream delgates to the reader.")]
        public void Flush_DelegatesToReader()
        {
            var reader = new Mock<IReader>();

            var stream = new SharedMemoryStream(new Mock<IDisposer<SharedMemoryStream>>().Object,
                                                new Mock<IWriter>().Object,
                                                reader.Object, null, null, null);
            stream.Flush();

            reader.Verify(r => r.Flush(), "Flush not called on the reader.");
        }

        [Test(Description = "Ending an async write operation delegates to the async result ender.")]
        public void EndWrite_DelegatesToAsyncResultEnder()
        {
            var ender = new Mock<IAsyncResultEnder>();
            var task = Task.Factory.StartNew(() => { });

            var stream = new SharedMemoryStream(new Mock<IDisposer<SharedMemoryStream>>().Object,
                                                new Mock<IWriter>().Object, null, null, ender.Object, null);
            stream.EndWrite(task);

            ender.Verify(e => e.EndTask(It.Is<Task>(t => t == task), It.IsAny<Func<AggregateException, Exception>>()),
                         "EndWrite did not delegate to the task ender.");
        }

        [Test(Description = "Reading a partial array delegates to the reader.")]
        public void Read_DelegatesToReader()
        {
            var buffer = new byte[5];
            const int offset = 2;
            const int count = 3;
            var reader = new Mock<IReader>();
//            reader.Setup(r => r.ReadAsync(It.IsAny<ArraySegment<byte>>(), It.IsAny<TimeSpan>()))
//                .Returns(new Task<int>(() => count));

            var stream = new SharedMemoryStream(new Mock<IDisposer<SharedMemoryStream>>().Object, null,
                                                reader.Object, null, null, null);
            stream.Read(buffer, offset, count);

            reader.Verify(
                r =>
                r.Read(It.Is<ArraySegment<byte>>(a => a.Array == buffer && a.Count == count && a.Offset == offset),
                       It.IsAny<TimeSpan>()),
                "Read not called on the reader.");
        }

        [Test(Description = "Begining an async read operation delegates to the reader.")]
        public void BeginRead_DelegatesToReader()
        {
            var buffer = new byte[5];
            const int offset = 2;
            const int count = 3;
            var reader = new Mock<IReader>();
            reader.Setup(r => r.ReadAsync(It.IsAny<ArraySegment<byte>>(), It.IsAny<TimeSpan>()))
                .Returns(new Task<int>(() => count));

            var stream = new SharedMemoryStream(new Mock<IDisposer<SharedMemoryStream>>().Object, null,
                                                reader.Object, null, null, null);
            stream.BeginRead(buffer, offset, count, null, null);

            reader.Verify(
                r =>
                r.ReadAsync(It.Is<ArraySegment<byte>>(a => a.Array == buffer && a.Count == count && a.Offset == offset),
                       It.IsAny<TimeSpan>()),
                "ReadAsync not called on the reader.");
            
        }

        [Test(Description = "Reading a byte delegates to the reader.")]
        public void ReadByte_DelegatesToReader()
        {
            var reader = new Mock<IReader>();
            reader.Setup(r => r.ReadByte(It.IsAny<TimeSpan>())).Returns('a');

            var stream = new SharedMemoryStream(new Mock<IDisposer<SharedMemoryStream>>().Object, null,
                                                reader.Object, null, null, null);
            stream.ReadByte();

            reader.Verify(r => r.ReadByte(It.IsAny<TimeSpan>()),
                          "ReadByte not called on the reader.");
        }

        [Test(Description = "Ending an async read operation delegates to the int async result ender.")]
        public void EndRead_DelegatesToIntAsyncReultEnder()
        {
            var ender = new Mock<IAsyncResultEnder<int>>();
            var task = Task.Factory.StartNew(() => { });

            var stream = new SharedMemoryStream(new Mock<IDisposer<SharedMemoryStream>>().Object,
                                                null, null, null, null, ender.Object);
            stream.EndRead(task);

            ender.Verify(e => e.EndTask(It.Is<Task>(t => t == task),
                                        It.IsAny<Func<AggregateException, Exception>>()),
                         "EndRead did not delegate to the task ender.");
        }

        [Test(Description = "Disconnecting the stream delegates to the disconnector.")]
        public void Disconnect_DelegatesToDisconnector()
        {
            var disconnector = new Mock<IDisconnector>();
            disconnector.Setup(d => d.Disconnect(It.IsAny<TimeSpan>())).Returns(new Task(() => { }));

            var stream = new SharedMemoryStream(new Mock<IDisposer<SharedMemoryStream>>().Object, null,
                                                null, disconnector.Object, null, null);
            stream.Disconnect();

            disconnector.Verify(d => d.Disconnect(It.IsAny<TimeSpan>()),
                                "Disconnect not called on the disconnector.");
        }

        [Test(Description = "Disconnecting the stream propegates a timeout exception thrown by the disconnector.")]
        public void Disconnect_PropegatesTimeoutException()
        {
            var disconnector = new Mock<IDisconnector>();
            disconnector.Setup(d => d.Disconnect(It.IsAny<TimeSpan>()))
                .Returns(new Task(() => { throw new TimeoutException(); }));

            var stream = new SharedMemoryStream(new Mock<IDisposer<SharedMemoryStream>>().Object, null,
                                                null, disconnector.Object, null, null);

            Assert.That(() =>
                            {
                                stream.Disconnect();
                                return null;
                            },
                        Throws.InstanceOf<TimeoutException>(),
                        "Disconnecting did not throw the expected exctption");
        }

        [Test(Description = "Disconnecting the stream propegates exceptions other than timeout excpetions " +
                            "thrown by the disconnector.")]
        public void Disconnect_PropegatesGeneralExecptions()
        {
            var disconnector = new Mock<IDisconnector>();
            disconnector.Setup(d => d.Disconnect(It.IsAny<TimeSpan>()))
                .Returns(new Task(() => { throw new InvalidOperationException(); }));

            var stream = new SharedMemoryStream(new Mock<IDisposer<SharedMemoryStream>>().Object, null,
                                                null, disconnector.Object, null, null);

            Assert.That(() =>
                            {
                                stream.Disconnect();
                                return null;
                            },
                        Throws.InstanceOf<SharedMemoryException>(),
                        "Disconnecting did not throw the expected exctption");
            
        }

        [Test(Description = "Begining an async disconnect operation delgates to the disconnector.")]
        public void BeginDisconnect_DelegatesToDisconnector()
        {
            var disconnector = new Mock<IDisconnector>();
            disconnector.Setup(d => d.Disconnect(It.IsAny<TimeSpan>())).Returns(new Task(() => { }));

            var stream = new SharedMemoryStream(new Mock<IDisposer<SharedMemoryStream>>().Object, null,
                                                null, disconnector.Object, null, null);
            stream.BeginDisconnect(null, null);

            disconnector.Verify(d => d.Disconnect(It.IsAny<TimeSpan>()),
                                "Disconnect not called on the disconnector.");
            
        }

        [Test(Description = "Ending an async disconnect operation delgates to the async result ender.")]
        public void EndDisconnect_DelegatesToAsyncResultEnder()
        {
            var ender = new Mock<IAsyncResultEnder>();
            var task = Task.Factory.StartNew(() => { });

            var stream = new SharedMemoryStream(new Mock<IDisposer<SharedMemoryStream>>().Object,
                                                null, null, null, ender.Object, null);
            stream.EndDisconnect(task);

            ender.Verify(e => e.EndTask(It.Is<Task>(t => t == task), It.IsAny<Func<AggregateException, Exception>>()),
                         "EndDisconnect did not delegate to the task ender.");
            
        }

    }
}